package au.edu.qut.bpm.genbinder.genetic.mutation;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.uncommons.maths.number.ConstantGenerator;
import org.uncommons.maths.number.NumberGenerator;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;

import au.edu.qut.bpm.genbinder.genetic.other.LogRepairer;
import au.edu.qut.bpm.genbinder.models.Activity;
import au.edu.qut.bpm.genbinder.models.Case;
import au.edu.qut.bpm.genbinder.models.ConcreteEventLog;

public class LogTimeShiftMutationUnsafeRepaired implements EvolutionaryOperator<ConcreteEventLog> {
	double delta = 1d;

	private final NumberGenerator<Integer> mutationCaseCountVariable;
	private final NumberGenerator<Integer> mutationActivityCountVariable;

	public LogTimeShiftMutationUnsafeRepaired() {
		this(3, 3);
	}

	public LogTimeShiftMutationUnsafeRepaired(int mutationCaseCount, int mutationActivityCount) {
		this(new ConstantGenerator<Integer>(mutationCaseCount),	new ConstantGenerator<Integer>(mutationActivityCount));
	}

	public LogTimeShiftMutationUnsafeRepaired(NumberGenerator<Integer> mutationCaseCount,	NumberGenerator<Integer> mutationActivityCount) {
		this.mutationCaseCountVariable = mutationCaseCount;
		this.mutationActivityCountVariable = mutationActivityCount;
		
	}

	public List<ConcreteEventLog> apply(List<ConcreteEventLog> selectedCandidates, Random rng) {
		List<ConcreteEventLog> mutatedCandidates = new ArrayList<ConcreteEventLog>(selectedCandidates.size());
		for (ConcreteEventLog log : selectedCandidates) {
			mutatedCandidates.add(mutate(log, rng));
		}
		return mutatedCandidates;
	}

	private ConcreteEventLog mutate(ConcreteEventLog log, Random rng) {
		ConcreteEventLog newLog = log.clone();

		int mutationCount = Math.abs(mutationCaseCountVariable.nextValue());
		while (mutationCount > 0) {
			int mutationAmount = Math.abs(mutationActivityCountVariable.nextValue());
			int c = rng.nextInt(newLog.getAbstractLog().getCases().size());
			Case cas = newLog.getAbstractLog().getCases().get(c);

			while (mutationAmount > 0) {
				List<Activity> trace = newLog.getSortedTrace(cas);
				int a = rng.nextInt(trace.size());
				Activity act = trace.get(a);

				long activityPossibleStartMin = 
						(a == 0) ? cas.getArrival()	: newLog.getActivityEnd(trace.get(a - 1));
				long activityPossibleStartMax = 
						(a == (trace.size() - 1)) ? (long) (newLog.getCaseEnd(cas) + act.getDuration())	: newLog.getActivityStart(trace.get(a + 1)) - act.getDuration();
				
				long diff = activityPossibleStartMax - activityPossibleStartMin;
				
				long newStart = 
						(diff <= 0)	? activityPossibleStartMin : activityPossibleStartMin + (long) rng.nextInt((int) (diff));
				
				newLog.setActivityStart(act, newStart);
				
				--mutationAmount;
			}
			--mutationCount;
		}

		if (!newLog.isLogSafe()) {
			List<ConcreteEventLog> result = new ArrayList<ConcreteEventLog>(1);
			result.add(newLog);
			
			LogRepairer repairer = new LogRepairer();
			result = repairer.apply(result, new Random());
			return result.get(0);
		}
		
		return newLog;
	}
}
